/*
 *  Pedometer - Android App
 *  Copyright (C) 2009 Levente Bagi
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.mDigitalHealth.service;

import java.util.Calendar;
import java.util.Date;

import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;

import com.mDigitalHealth.R;
import com.mDigitalHealth.activity.MainActivityGroup;
import com.mDigitalHealth.activity.SportActivity;
import com.mDigitalHealth.common.Utils;
import com.mDigitalHealth.count.AlarmReceiver;
import com.mDigitalHealth.count.CaloriesNotifier;
import com.mDigitalHealth.count.DistanceNotifier;
import com.mDigitalHealth.count.StepDetector;
import com.mDigitalHealth.count.StepDisplayer;
import com.mDigitalHealth.settings.PedometerSettings;
import com.mDigitalHealth.thread.HourSaveThread;
import com.mDigitalHealth.thread.UploadThread;

/**
 * This is an example of implementing an application service that runs locally in the same process
 * as the application. The {@link StepServiceController} and {@link StepServiceBinding} classes show
 * how to interact with the service.
 * 
 * <p>
 * Notice the use of the {@link NotificationManager} when interesting things happen in the service.
 * This is generally how background services should interact with the user, rather than doing
 * something more disruptive such as calling startActivity().
 */
public class StepService extends Service {
	private static final String TAG = "com.mDigitalHealth.service.StepService";
	private SharedPreferences mSettings;
	private PedometerSettings mPedometerSettings;
	private SharedPreferences mState;
	private SharedPreferences.Editor mStateEditor;
	private Utils mUtils;
	private SensorManager mSensorManager;
	private Sensor mSensor;
	private StepDetector mStepDetector;
	// private StepBuzzer mStepBuzzer; // used for debugging
	private StepDisplayer mStepDisplayer; // baoliu
	private DistanceNotifier mDistanceNotifier; // baoliu
	private CaloriesNotifier mCaloriesNotifier; // baoliu

	private PowerManager.WakeLock wakeLock;
	private NotificationManager mNM;

	private static int mSteps;
	private int mPace;
	private float mDistance;
	private float mSpeed;
	private float mCalories;

	private int hourSteps;
	private long hourTime;

	private AlarmManager mAlarmManager;
	private PendingIntent pendingIntentUpload, pendingIntentSaveInfo;

	private ResetValuesBroadcastReceiver mResetValuesBroadcastReceiver;

	/**
	 * Class for clients to access. Because we know this service always runs in the same process as
	 * its clients, we don't need to deal with IPC.
	 */
	public class StepBinder extends Binder {
		public StepService getService() {
			return StepService.this;
		}
	}

	@Override
	public void onCreate() {
		Log.i(TAG, "[SERVICE] onCreate");
		super.onCreate();

		mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
		showNotification();

		// Load settings
		mSettings = PreferenceManager.getDefaultSharedPreferences(this);
		mPedometerSettings = new PedometerSettings(mSettings);
		mState = getSharedPreferences("state", 0);
		mStateEditor = mState.edit();

		mUtils = Utils.getInstance();
		mUtils.setService(this);

		acquireWakeLock();

		// Start detecting
		mStepDetector = new StepDetector();
		try {
			// if (isDevice()) {

			mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
			// }
		} catch (Exception e) {
			e.printStackTrace();
		}
		registerDetector();

		// Register our receiver for the ACTION_SCREEN_OFF action. This will make our receiver
		// code be called whenever the phone enters standby mode.
		IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
		registerReceiver(mReceiver, filter);

		mStepDisplayer = new StepDisplayer(mPedometerSettings, mUtils);
		mStepDisplayer.setSteps(mSteps = mState.getInt("steps", 0));
		mStepDisplayer.addListener(mStepListener);
		mStepDetector.addStepListener(mStepDisplayer);

		mDistanceNotifier = new DistanceNotifier(mDistanceListener, mPedometerSettings, mUtils);
		mDistanceNotifier.setDistance(mDistance = mState.getFloat("distance", 0));
		mStepDetector.addStepListener(mDistanceNotifier);

		mCaloriesNotifier = new CaloriesNotifier(mCaloriesListener, mPedometerSettings, mUtils);
		mCaloriesNotifier.setCalories(mCalories = mState.getFloat("calories", 0));
		mStepDetector.addStepListener(mCaloriesNotifier);

		// Used when debugging:
		// mStepBuzzer = new StepBuzzer(this);
		// mStepDetector.addStepListener(mStepBuzzer);

		// Start voice
		reloadSettings();

		registerAlarm();

		registerValuesReset();

		Calendar mCalendarHourNow = Calendar.getInstance();
		hourTime = mState.getLong("hour_time", mCalendarHourNow.getTimeInMillis());

		if (hourTime == mCalendarHourNow.getTimeInMillis()) { // 没有保存，即第一次运行
			mStateEditor.putInt("hour_steps", 0).commit();
			Calendar mCalendarTmp = Calendar.getInstance();
			mCalendarTmp.set(Calendar.MINUTE, 0);
			mStateEditor.putLong("hour_time", mCalendarTmp.getTimeInMillis()).commit();
		} else {// 有保存，即之前已经开启过记步软件
			Calendar mCalendarHourOld = Calendar.getInstance();
			mCalendarHourOld.setTime(new Date(hourTime));
			if (mCalendarHourNow.getTimeInMillis() - mCalendarHourOld.getTimeInMillis() > 3600 * 1000) {
				// 时间差大于1个小时，保存步数
				new HourSaveThread(StepService.this, mState, mStateEditor, mSteps);
			} else {
				// 时间差小于1个小时
				if (Math.abs(mCalendarHourNow.get(Calendar.HOUR_OF_DAY)
						- mCalendarHourOld.get(Calendar.HOUR_OF_DAY)) > 0) {
					// 不是同一个小时
					new HourSaveThread(StepService.this, mState, mStateEditor, mSteps);
				}
			}
		}

		hourSteps = mState.getInt("hour_steps", 0);

		Calendar mCalendarOld = Calendar.getInstance();
		mCalendarOld.setTime(new Date(hourTime));
		if (mCalendarHourNow.getTimeInMillis() - mCalendarOld.getTimeInMillis() > 3600 * 1000 * 24)
			new UploadThread(StepService.this, mState, mStateEditor).start();
		else {
			if (mCalendarHourNow.get(Calendar.DAY_OF_MONTH) == mCalendarOld
					.get(Calendar.DAY_OF_MONTH)) {
				if ((mCalendarHourNow.get(Calendar.HOUR_OF_DAY) > Utils.UPLOAD_HOUR)
						&& (mCalendarOld.get(Calendar.HOUR_OF_DAY) < Utils.UPLOAD_HOUR)) {
					new UploadThread(StepService.this, mState, mStateEditor).start();
				}
			} else {
				if (mCalendarHourNow.get(Calendar.HOUR_OF_DAY) > Utils.UPLOAD_HOUR) {
					new UploadThread(StepService.this, mState, mStateEditor).start();
				}
			}
		}

		// Tell the user we started.
		Toast.makeText(this, getText(R.string.started), Toast.LENGTH_SHORT).show();
	}

	/**
	 * Determine if the app is running with a device or an emulator.
	 */
	public boolean isDevice() {
		return !"sdk".equals(Build.MODEL) && !"sdk".equals(Build.PRODUCT)
				&& !"generic".equals(Build.DEVICE);
	}

	@Override
	public void onStart(Intent intent, int startId) {
		Log.i(TAG, "[SERVICE] onStart");
		super.onStart(intent, startId);
	}

	@Override
	public void onDestroy() {
		Log.i(TAG, "[SERVICE] onDestroy");

		// Unregister our receiver.
		unregisterReceiver(mReceiver);
		unregisterDetector();

		mStateEditor = mState.edit();
		mStateEditor.putInt("steps", mSteps);
		mStateEditor.putInt("pace", mPace);
		mStateEditor.putFloat("distance", mDistance);
		mStateEditor.putFloat("speed", mSpeed);
		mStateEditor.putFloat("calories", mCalories);
		mStateEditor.commit();

		mNM.cancel(R.string.app_name);

		wakeLock.release();

		super.onDestroy();

		// Stop detecting
		mSensorManager.unregisterListener(mStepDetector);

		unRegisterAlarm();

		unRegisterValueReset();

		// Tell the user we stopped.
		Toast.makeText(this, getText(R.string.stopped), Toast.LENGTH_SHORT).show();
	}

	private void registerDetector() {
		mSensor = mSensorManager
				.getDefaultSensor(Sensor.TYPE_ACCELEROMETER /*| 
															Sensor.TYPE_MAGNETIC_FIELD | 
															Sensor.TYPE_ORIENTATION*/);
		mSensorManager.registerListener(mStepDetector, mSensor, SensorManager.SENSOR_DELAY_FASTEST);
	}

	private void unregisterDetector() {
		mSensorManager.unregisterListener(mStepDetector);
	}

	private void registerAlarm() {
		Calendar mCalendar = Calendar.getInstance();
		// System.out.println("**current time=" + Utils.getFormatTime(mCalendar.getTimeInMillis()));
		mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
		int day = mCalendar.get(Calendar.DAY_OF_MONTH);
		int mHour = mCalendar.get(Calendar.HOUR_OF_DAY);
		int minute = mCalendar.get(Calendar.MINUTE);
		if (mHour > Utils.UPLOAD_HOUR)
			mCalendar.set(Calendar.DAY_OF_MONTH, day + 1);
		mCalendar.set(Calendar.HOUR_OF_DAY, Utils.UPLOAD_HOUR);
		mCalendar.set(Calendar.MINUTE, Utils.UPLOAD_MINUTE);
		mCalendar.set(Calendar.SECOND, Utils.UPLOAD_SECEND);
		// System.out.println("**current time=" + Utils.getFormatTime(mCalendar.getTimeInMillis()));
		Intent intentUpload = new Intent(StepService.this, AlarmReceiver.class)
				.setAction(Utils.ALARM_MANAGER_UPLOAD_ACTION);
		pendingIntentUpload = PendingIntent.getBroadcast(StepService.this,
				Utils.PENDINGINTENT_REQUEST_CODE_UPLOAD, intentUpload, 0);
		mAlarmManager
				.set(AlarmManager.RTC_WAKEUP, mCalendar.getTimeInMillis(), pendingIntentUpload);
		mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, mCalendar.getTimeInMillis()
				+ (10 * 1000), (24 * 60 * 60 * 1000), pendingIntentUpload);

		mCalendar.set(Calendar.DAY_OF_MONTH, day);
		mCalendar.set(Calendar.HOUR_OF_DAY, mHour + 1);
		mCalendar.set(Calendar.MINUTE, 0);
		// System.out.println("**current time=" + Utils.getFormatTime(mCalendar.getTimeInMillis()));
		Intent intentSaveInfo = new Intent(StepService.this, AlarmReceiver.class)
				.setAction(Utils.ALARM_MANAGER_SAVE_INFO);
		pendingIntentSaveInfo = PendingIntent.getBroadcast(StepService.this,
				Utils.PENDINGINTENT_REQUEST_CODE_SAVE_INFO, intentSaveInfo,
				PendingIntent.FLAG_CANCEL_CURRENT);
		mAlarmManager.set(AlarmManager.RTC_WAKEUP, mCalendar.getTimeInMillis(),
				pendingIntentSaveInfo);
		mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, mCalendar.getTimeInMillis()
				+ (1 * 1000), (3600 * 1000), pendingIntentSaveInfo);
	}

	private void unRegisterAlarm() {
		if (mAlarmManager != null) {
			mAlarmManager.cancel(pendingIntentUpload);
			mAlarmManager.cancel(pendingIntentSaveInfo);
		}
	}

	private void registerValuesReset() {
		IntentFilter filter = new IntentFilter(Utils.BROADCAST_ACTION_RESET_VALUES);
		mResetValuesBroadcastReceiver = new ResetValuesBroadcastReceiver();
		registerReceiver(mResetValuesBroadcastReceiver, filter);
	}

	private void unRegisterValueReset() {
		if (mResetValuesBroadcastReceiver != null) {
			unregisterReceiver(mResetValuesBroadcastReceiver);
			mResetValuesBroadcastReceiver = null;
		}
	}

	@Override
	public IBinder onBind(Intent intent) {
		Log.i(TAG, "[SERVICE] onBind");
		return mBinder;
	}

	/**
	 * Receives messages from activity.
	 */
	private final IBinder mBinder = new StepBinder();

	public interface ICallback {
		public void stepsChanged(int value);

		public void distanceChanged(float value);

		public void caloriesChanged(float value);
	}

	private ICallback mCallback;

	public void registerCallback(ICallback cb) {
		Log.i(TAG, "[SERVICE] registerCallback");
		mCallback = cb;
		// mStepDisplayer.passValue();
		// mPaceListener.passValue();
	}

	private int mDesiredPace;
	private float mDesiredSpeed;

	public void reloadSettings() {
		mSettings = PreferenceManager.getDefaultSharedPreferences(this);

		if (mStepDetector != null) {
			mStepDetector.setSensitivity(Float.valueOf(mSettings.getString("sensitivity", "10")));
		}

		if (mStepDisplayer != null)
			mStepDisplayer.reloadSettings();
		if (mDistanceNotifier != null)
			mDistanceNotifier.reloadSettings();
		if (mCaloriesNotifier != null)
			mCaloriesNotifier.reloadSettings();
	}

	public void resetValues() {
		mStepDisplayer.setSteps(0);
		mDistanceNotifier.setDistance(0);
		mCaloriesNotifier.setCalories(0);
	}

	/**
	 * Forwards pace values from PaceNotifier to the activity.
	 */
	private StepDisplayer.Listener mStepListener = new StepDisplayer.Listener() {
		public void stepsChanged(int value) {
			Log.i(TAG, "[SERVICE] mStepListener-->stepsChanged--value=" + value);
			mSteps = value;
			passValue();
		}

		public void passValue() {
			if (mCallback != null) {
				mCallback.stepsChanged(mSteps);
			} else {
				Intent intent = new Intent();
				intent.setAction(SportActivity.STEPS_CHANGED_ACTION);
				intent.putExtra("VALUE", mSteps);
				sendBroadcast(intent);
			}
		}
	};

	/**
	 * Forwards distance values from DistanceNotifier to the activity.
	 */
	private DistanceNotifier.Listener mDistanceListener = new DistanceNotifier.Listener() {
		public void valueChanged(float value) {
			mDistance = value;
			passValue();
		}

		public void passValue() {
			if (mCallback != null) {
				mCallback.distanceChanged(mDistance);
			} else {
				Intent intent = new Intent();
				intent.setAction(SportActivity.METER_CHANGED_ACTION);
				intent.putExtra("VALUE", mDistance);
				sendBroadcast(intent);
			}
		}
	};

	/**
	 * Forwards calories values from CaloriesNotifier to the activity.
	 */
	private CaloriesNotifier.Listener mCaloriesListener = new CaloriesNotifier.Listener() {
		public void valueChanged(float value) {
			mCalories = value;
			passValue();
		}

		public void passValue() {
			if (mCallback != null) {
				mCallback.caloriesChanged(mCalories);
			} else {
				Intent intent = new Intent();
				intent.setAction(SportActivity.CALORY_CHANGED_ACTION);
				intent.putExtra("VALUE", mCalories);
				sendBroadcast(intent);
			}
		}
	};

	/**
	 * Show a notification while this service is running.
	 */
	private void showNotification() {
		CharSequence text = getText(R.string.app_name);
		Notification notification = new Notification(R.drawable.ic_notification, null,
				System.currentTimeMillis());
		notification.flags = Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT;
		Intent pedometerIntent = new Intent();
		pedometerIntent.setComponent(new ComponentName(this, MainActivityGroup.class));
		pedometerIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
		PendingIntent contentIntent = PendingIntent.getActivity(this, 0, pedometerIntent, 0);
		notification.setLatestEventInfo(this, text, getText(R.string.notification_subtitle),
				contentIntent);

		mNM.notify(R.string.app_name, notification);
	}

	// BroadcastReceiver for handling ACTION_SCREEN_OFF.
	private BroadcastReceiver mReceiver = new BroadcastReceiver() {
		@Override
		public void onReceive(Context context, Intent intent) {
			// Check action just to be on the safe side.
			if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
				// Unregisters the listener and registers it again.
				StepService.this.unregisterDetector();
				StepService.this.registerDetector();
				if (mPedometerSettings.wakeAggressively()) {
					wakeLock.release();
					acquireWakeLock();
				}
			}
		}
	};

	public class ResetValuesBroadcastReceiver extends BroadcastReceiver {

		@Override
		public void onReceive(Context context, Intent intent) {
			String action = intent.getAction();
			if (Utils.BROADCAST_ACTION_RESET_VALUES.equalsIgnoreCase(action)) {
				// System.out.println("***reset step values");
				resetValues();
			}
		}

	}

	private void acquireWakeLock() {
		PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
		int wakeFlags;
		if (mPedometerSettings.wakeAggressively()) {
			wakeFlags = PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
		} else if (mPedometerSettings.keepScreenOn()) {
			wakeFlags = PowerManager.SCREEN_DIM_WAKE_LOCK;
		} else {
			wakeFlags = PowerManager.PARTIAL_WAKE_LOCK;
		}
		wakeLock = pm.newWakeLock(wakeFlags, TAG);
		wakeLock.acquire();
	}

	public static int getSteps() {
		// System.out.println("***step service-- > current steps=" + mSteps);
		return mSteps;
	}

}
